const std = @import("std");
const curses = @cImport({
	@cInclude("curses.h");
});

const gridLib = @import("grid.zig");
const Grid = @import("grid.zig").Grid;
const Snake = @import("snake.zig").Snake;
const Apple = @import("apple.zig").Apple;
const Direction = @import("direction.zig").Direction;

const EndConditions = enum {SELF_COLLISION, BOUNDARY_COLLISION, WON};
var last_direction: Direction = undefined;


pub fn main_loop(grid: *Grid, snake: *Snake) !void {
	last_direction = Direction{.up = true};
	var apple = Apple.generate(grid.width - 1, grid.height - 1,
			snake.get_cells());
	while (true) {
		if (try step(grid, snake, &apple)) |end_condition| {
			switch (end_condition) {
				EndConditions.SELF_COLLISION,
				EndConditions.BOUNDARY_COLLISION => {
					game_over();
					break;
				},
				EndConditions.WON => {
					game_won();
					break;
				}
			}
		}
		std.time.sleep(std.time.ns_per_s / 3);
	}
}


fn step(grid: *Grid, snake: *Snake, apple: *Apple) !?EndConditions {
	const direction: Direction = get_direction();
	last_direction = direction;
	try snake.move(direction);

	if (snake_on_apple(snake, apple)) {
		try snake.add_cell();
		apple.* = Apple.generate(grid.width - 1, grid.height - 1,
				snake.get_cells());
	}

	grid.clear();
	for (snake.get_cells()) |cell| {
		grid.set_cell(cell[0], cell[1], gridLib.CellTypes.SNAKE);
	}
	grid.set_cell(apple.x, apple.y, gridLib.CellTypes.APPLE);
	_ = curses.clear();
	grid.display();

	if (has_won(grid, snake)) {
		return EndConditions.WON;
	}
	if (!snake_in_bounds(grid, snake)) {
		return EndConditions.BOUNDARY_COLLISION;
	}
	if (snake_collided_self(snake)) {
		return EndConditions.SELF_COLLISION;
	}
	return null;
}


fn get_direction() Direction {
	_ = curses.getch();
	_ = curses.getch();
	const key: c_int = curses.getch();
	_ = curses.flushinp();

	var direction = Direction{};

	switch (key) {
		65 => direction.up = true,
		66 => direction.down = true,
		67 => direction.right = true,
		68 => direction.left = true,
		else => {},
	}

	if (!direction.any()) {
		direction = last_direction;
	}

	return direction;
}


fn snake_in_bounds(grid: *Grid, snake: *Snake) bool {
	const head: [2]u8 = snake.get_head() orelse return true;
	if (head[0] >= grid.width or head[1] >= grid.height
	) {
		return false;
	}
	return true;
}


fn snake_on_apple(snake: *Snake, apple: *Apple) bool {
	for (snake.get_cells()) |snake_cell| {
		if (snake_cell[0] == apple.x and snake_cell[1] == apple.y) {
			return true;
		}
	}
	return false;
}


fn snake_collided_self(snake: *Snake) bool {
	const head: [2]u8 = snake.get_head() orelse return false;
	for (1..snake.get_size()) |i| {
		const body_cell: [2]u8 = snake.get_cells()[i];
		if (head[0] == body_cell[0] and head[1] == body_cell[1]) {
			return true;
		}
	}
	return false;
}


fn has_won(grid: *Grid, snake: *Snake) bool {
	return @as(u16, grid.width) * grid.height == snake.get_size();
}


fn game_over() void {
	curses.timeout(-1);

	_ = curses.clear();
	_ = curses.attron(curses.COLOR_PAIR(1));
	_ = curses.addstr("\tGAME OVER\n\rPress ENTER to continue");

	var key: c_int = curses.getch();
	while (key != 10) {
		key = curses.getch();
	}

	curses.timeout(0);
}


fn game_won() void {
	curses.timeout(-1);

	_ = curses.clear();
	_ = curses.attron(curses.COLOR_PAIR(1));
	_ = curses.addstr("\tYOU WIN\n\rPress ENTER to continue");

	var key: c_int = curses.getch();
	while (key != 10) {
		key = curses.getch();
	}

	curses.timeout(0);
}
